/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-02-08     RT-Thread    the first version
 */
#include <rtthread.h>
#include <rthw.h>

#include "drv_gpio.h"
#include "interrupt.h"

#define DBG_TAG  "GPIO"
#define DBG_LVL  DBG_WARNING
#include <rtdbg.h>


#define T113_INTERRUPT_PIOB  101
#define T113_INTERRUPT_PIOC  103
#define T113_INTERRUPT_PIOD  105
#define T113_INTERRUPT_PIOE  107
#define T113_INTERRUPT_PIOF  109
#define T113_INTERRUPT_PIOG  111

#define readl(addr)           (*(volatile unsigned int *)(addr))
#define writel(value,addr)    (*(volatile unsigned int *)(addr) = (value))


// Todo: add RT_ASSERT.

/*********************************************************
**   IO
*********************************************************/
int gpio_set_func(enum gpio_port port, enum gpio_pin pin, rt_uint8_t func)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    RT_ASSERT((0 <= port) && (port < GPIO_PORT_NUM));
    RT_ASSERT((GPIO_PIN_0 <= pin) && (pin < GPIO_PIN_NUM));


    addr = GPIOn_CFG_ADDR(port) + (pin / 8) * 4;
    offset = (pin % 8) * 4;

    data = readl(addr);
    data &= ~(0xf << offset);
    data |= func << offset;
    writel(data, addr);

    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
    return RT_EOK;
}

int gpio_set_value(enum gpio_port port, enum gpio_pin pin, rt_uint8_t value)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    RT_ASSERT((0 <= port) && (port < GPIO_PORT_NUM));
    RT_ASSERT((GPIO_PIN_0 <= pin) && (pin < GPIO_PIN_NUM));

    if (value & 0xE)
    {
        LOG_W("[line]:%d There is a warning with parameter input", __LINE__);
        return RT_EINVAL;
    }

    addr = GPIOn_DATA_ADDR(port);
    offset = pin;

    data = readl(addr);
    data &= ~(0x1 << offset);
    data |= value << offset;
    writel(data, addr);

    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
    return RT_EOK;
}

int gpio_get_value(enum gpio_port port, enum gpio_pin pin)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    RT_ASSERT((0 <= port) && (port < GPIO_PORT_NUM));
    RT_ASSERT((GPIO_PIN_0 <= pin) && (pin < GPIO_PIN_NUM));

    addr = GPIOn_DATA_ADDR(port);
    offset = pin;

    data = readl(addr);

    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
    return (data >> offset) & 0x01;
}

int gpio_set_pull_mode(enum gpio_port port,  enum gpio_pin pin, enum gpio_pull pull)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    RT_ASSERT((0 <= port) && (port < GPIO_PORT_NUM));
    RT_ASSERT((GPIO_PIN_0 <= pin) && (pin < GPIO_PIN_NUM));

    if (pull & 0xC)
    {
        LOG_W("[line]:%d There is a warning with parameter input", __LINE__);
        return RT_EINVAL;
    }

    addr = GPIOn_PUL_ADDR(port);
    addr += pin > GPIO_PIN_15 ? 0x4 : 0x0;
    offset = (pin & 0xf) << 1;

    data = readl(addr);
    data &= ~(0x3 << offset);
    data |= pull << offset;
    writel(data, addr);

    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
    return RT_EOK;
}

int gpio_set_drive_level(enum gpio_port port, enum gpio_pin pin, enum gpio_drv_level level)
{
    volatile rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    RT_ASSERT((0 <= port) && (port < GPIO_PORT_NUM));
    RT_ASSERT((GPIO_PIN_0 <= pin) && (pin < GPIO_PIN_NUM));

    if (level & 0xC)
    {
        LOG_W("[line]:%d There is a warning with parameter input", __LINE__);
        return RT_EINVAL;
    }

    addr = GPIOn_DRV_ADDR(port);
    addr += pin > GPIO_PIN_15 ? 0x4 : 0x0;
    offset = (pin & 0xf) << 1;

    data = readl(addr);
    data &= ~(0x3 << offset);
    data |= level << offset;
    writel(data, addr);

    LOG_I("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
    return RT_EOK;
}

void gpio_direction_input(enum gpio_port port, enum gpio_pin pin)
{
    gpio_set_func(port, pin, IO_INPUT);
}

void gpio_direction_output(enum gpio_port port, enum gpio_pin pin, int value)
{
    gpio_set_value(port, pin, value);
    gpio_set_func(port, pin, IO_OUTPUT);
}
/*********************************************************
**   IRQ
*********************************************************/
/* configuration */
void gpio_irq_set_type(enum gpio_port port,  enum gpio_pin pin, enum gpio_irq_type irq_type)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    addr = GPIOn_INT_CFG_ADDR(port) + (pin / 8) * 4;
    offset = (pin % 8) * 4;

    data = readl(addr);
    data &= ~(0xf << offset);
    data |= irq_type << offset;
    writel(data, addr);

    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
}

/* control */
void gpio_irq_enable(enum gpio_port port,  enum gpio_pin pin)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;
    addr = GPIOn_INT_CTRL_ADDR(port);
    offset = pin;

    data = readl(addr);
    data |= 0x1 << offset;
    writel(data, addr);
    gpio_set_debounce(port, GPIO_IRQ_HOSC_24MHZ, DEBOUNCE_PRE_SCALE_1);
    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
}

/* state */
void gpio_irq_clear(enum gpio_port port,  enum gpio_pin pin)
{
    rt_uint32_t addr;
    rt_uint32_t data;

    addr = GPIOn_INT_STA_ADDR(port);
    data = readl(addr);
    data |= 0x1 << pin;
    writel(data, addr);
}

void gpio_irq_disable(enum gpio_port port,  enum gpio_pin pin)
{
    rt_uint32_t addr;
    rt_uint32_t offset;
    rt_uint32_t data;

    gpio_irq_clear(port, pin);
    addr = GPIOn_INT_CTRL_ADDR(port);
    offset = pin;

    data = readl(addr);
    data &= ~(0x1 << offset);

    writel(data, addr);
    LOG_D("[line]:%d offset:%d addr:%08x data:%08x", __LINE__, offset, addr, *((rt_uint32_t *)addr));
}



/* debounce */
void gpio_set_debounce(enum gpio_port port, enum gpio_debounce_clock clock, enum gpio_debounce_prescale prescale)
{
    rt_uint32_t addr;
    rt_uint32_t data;

    addr = GPIOn_INT_DEB_ADDR(port);
    data = readl(addr);
    data &= ~0x71;
    data |= (prescale << 4) | clock;
    writel(data, addr);
    LOG_D("[line]:%d addr:%08x data:%08x", __LINE__, addr, *((rt_uint32_t *)addr));
}

/* gpio interrupt handlers for each pin */
static struct gpio_irq_def _g_gpio_irq_tbl[GPIO_PORT_NUM];

void gpio_irq_set_callback(enum gpio_port port, enum gpio_pin pin, void (*irq_cb)(void *), void *irq_arg)
{
    RT_ASSERT((GPIO_PIN_0 <= pin) && (pin < GPIO_PIN_NUM));

    _g_gpio_irq_tbl[port].irq_cb[pin]    = irq_cb;
    _g_gpio_irq_tbl[port].irq_arg[pin]   = irq_arg;
}

void gpio_irq_clear_callback(enum gpio_port port, enum gpio_pin pin)
{
    gpio_irq_disable(port, pin);

    _g_gpio_irq_tbl[port].irq_cb[pin]    = RT_NULL;
    _g_gpio_irq_tbl[port].irq_arg[pin]   = RT_NULL;
}

static void gpio_irq_handler(int irq, void *param)
{
    struct gpio_irq_def *irq_def = (struct gpio_irq_def *)param;
    rt_uint32_t pend, enable;
    int  port, pin;

    rt_uint32_t addr;

    pin = 0;

    switch (irq)
    {
    case T113_INTERRUPT_PIOB:
        port = GPIO_PORT_B;
        break;
    case T113_INTERRUPT_PIOC:
        port = GPIO_PORT_C;
        break;
    case T113_INTERRUPT_PIOD:
        port = GPIO_PORT_D;
        break;
    case T113_INTERRUPT_PIOE:
        port = GPIO_PORT_E;
        break;
    case T113_INTERRUPT_PIOF:
        port = GPIO_PORT_F;
        break;
    case T113_INTERRUPT_PIOG:
        port = GPIO_PORT_G;
        break;
    
    default:
        break;
    }
    
    addr = GPIOn_INT_STA_ADDR(port);
    pend = readl(addr);
    addr = GPIOn_INT_CTRL_ADDR(port);
    enable = readl(addr);
    pend &= enable;

    while (pend)
    {
        if ((pend & 0x1) && (irq_def->irq_cb[pin] != RT_NULL))
        {
            LOG_D("do irq callback...", port, pin);
            irq_def->irq_cb[pin](irq_def->irq_arg[pin]);
        }
        pin++;
        pend = pend >> 1;
        gpio_irq_clear(port, (enum gpio_pin )pin);
    }
}

int rt_hw_gpio_init(void)
{
#ifdef RT_USING_PIN
    rt_device_pin_register("pin", &ops, RT_NULL);
#endif
    /* install ISR */
    rt_hw_interrupt_install(T113_INTERRUPT_PIOB, gpio_irq_handler, &_g_gpio_irq_tbl[GPIO_PORT_B], "gpiod_irq");
    rt_hw_interrupt_umask(T113_INTERRUPT_PIOB);
    rt_hw_interrupt_install(T113_INTERRUPT_PIOC, gpio_irq_handler, &_g_gpio_irq_tbl[GPIO_PORT_C], "gpiog_irq");
    rt_hw_interrupt_umask(T113_INTERRUPT_PIOC);
    rt_hw_interrupt_install(T113_INTERRUPT_PIOD, gpio_irq_handler, &_g_gpio_irq_tbl[GPIO_PORT_D], "gpiog_irq");
    rt_hw_interrupt_umask(T113_INTERRUPT_PIOD);
    rt_hw_interrupt_install(T113_INTERRUPT_PIOE, gpio_irq_handler, &_g_gpio_irq_tbl[GPIO_PORT_E], "gpiog_irq");
    rt_hw_interrupt_umask(T113_INTERRUPT_PIOE);
    rt_hw_interrupt_install(T113_INTERRUPT_PIOF, gpio_irq_handler, &_g_gpio_irq_tbl[GPIO_PORT_F], "gpiog_irq");
    rt_hw_interrupt_umask(T113_INTERRUPT_PIOF);
    rt_hw_interrupt_install(T113_INTERRUPT_PIOG, gpio_irq_handler, &_g_gpio_irq_tbl[GPIO_PORT_G], "gpiog_irq");
    rt_hw_interrupt_umask(T113_INTERRUPT_PIOG);

    return 0;
}
INIT_DEVICE_EXPORT(rt_hw_gpio_init);

